﻿#region - Using directives -

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Tools.Ribbon;
using Zen4SyncReport.DAL;
using System.Windows.Forms;
using Zen4SyncReport.Report;
using Excel = Microsoft.Office.Interop.Excel;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Interop.Excel;
using System.Threading.Tasks;
using Zen4SyncReport.Forms;

#endregion - Using directives -

namespace Zen4SyncReport
{
    /// <summary>
    /// This class represents the one and only tab of the Zen4SyncReport AddIn.
    /// </summary>
    public partial class Zen4SyncRibbon
    {
        #region - Nested types -

        /// <summary>
        /// This class contains all the messages displayed to the user.
        /// </summary>
        private static class Messages
        {
            /// <summary>
            /// Message saying that no Test Session has been found.
            /// </summary>
            public const string NoTestSessionsFound = "No Test Sessions match your criterias.";
            /// <summary>
            /// Message saying that the user must search for Test Sessions.
            /// </summary>
            public const string SearchForTestSessions = "Please search for Test Sessions before trying to generate a report.";            
            /// <summary>
            /// Message asking the user to confirm he wants to generate the title.
            /// </summary>
            public const string ConfirmReportGeneration = "Report generation will delete ALL the data of the active Workbook.\nAre you sure you want to generate the report ?";
            /// <summary>
            /// Title associated to the message asking the user confirmation to generate the report.
            /// </summary>
            public const string ConfirmReportGenerationTitle = "Confirm report generation";
            /// <summary>
            /// Message saying that Login is mandatory if not using Integrated Security.
            /// </summary>
            public const string MandatoryDbLogin = "Please enter a valid login if you don't use Integrated Security.";
            /// <summary>
            /// Message saying that Password is mandatory if not using Integrated Security.
            /// </summary>
            public const string MandatoryDbPassword = "Please enter a valid password if you don't use Integrated Security.";
            /// <summary>
            /// Message saying that the server name must be valid.
            /// </summary>
            public const string MandatoryDbServer = "Please enter a valid Server Address.";
            /// <summary>
            /// Message saying that an error occured while connecting to the database.
            /// </summary>
            public const string DbConnectionError = "An error occured while connecting to the database.";            
            /// <summary>
            /// Message saying that at least one search criteria must be entered.
            /// </summary>
            public const string MandatorySearchCriteria = "Please enter at least one search criteria.";
        }

        #endregion - Nested types -

        #region - Constants -

        /// <summary>
        /// The Tag value of the unique DropDownItem filling the DropDownList of Test Sessions when no Test Session has been found
        /// or no search has been performed yet.
        /// </summary>
        private const string EmptyTestSessionId = "EmptyTestSession";

        /// <summary>
        /// The Label value of the unique DropDownItem filling the DropDownList of Test Sessions when no Test Session has been found
        /// or no search has been performed yet.
        /// </summary>
        private const string EmtpyTestSessionLabel = "Please search for Test Sessions...";

        #endregion - Constants -
        
        #region - Private fields -

        /// <summary>
        /// Report Generator used to generate reports.
        /// </summary>
        private ReportGenerator _ReportGenerator;

        /// <summary>
        /// Communication with Zen4SyncRepository.
        /// </summary>
        private Zen4SyncDAL _DAL;

        /// <summary>
        /// Backing field for the password entered by the user.
        /// Needed to emulate the existence of "password char" in the Ribbon EditBox.
        /// </summary>
        private string password;

        #endregion - Private fields -

        #region - Constructors and Initialization -

        /// <summary>
        /// User wants to enter its password.
        /// </summary>
        /// <param name="sender">"Password" button.</param>
        /// <param name="e"></param>
        private void Ctrl_ButtonPassword_Click(object sender, RibbonControlEventArgs e)
        {
            PasswordForm pwdForm = new PasswordForm();

            DialogResult result = pwdForm.ShowDialog();

            if (result == DialogResult.OK)
                password = pwdForm.Password;
        }

        /// <summary>
        /// Ribbon load.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Zen4SyncRibbon_Load(object sender, RibbonUIEventArgs e)
        {
            password = String.Empty;
            InitUI(false);
        }

        #endregion - Constructors and Initialization -

        #region - Events -

        /// <summary>
        /// User wants to connect to the database.
        /// </summary>
        /// <param name="sender">The "Connect" button.</param>
        /// <param name="e"></param>
        private void Ctrl_ButtonConnect_Click(object sender, RibbonControlEventArgs e)
        {
            ConnectToDatabase(
                this.Ctrl_EditBoxServer.Text,
                this.Ctrl_ToggleIntegratedSecurity.Checked,
                this.Ctrl_EditBoxDbLogin.Text,
                password);
        }

        /// <summary>
        /// User wants to search the Test Sessions with the selected criterias.
        /// </summary>
        /// <param name="sender">Button "Search".</param>
        /// <param name="e"></param>
        private void Ctrl_ButtonSearch_Click(object sender, RibbonControlEventArgs e)
        {
            SearchTestSessions(
                (String)this.Ctrl_DropDownTestSessionCategories.SelectedItem.Tag,
                this.Ctrl_EditBoxTestSessionName.Text);
        }

        /// <summary>
        /// User wants to generate a report for the selected Test Session.
        /// </summary>
        /// <param name="sender">Button "Generate Report".</param>
        /// <param name="e"></param>
        private void Ctrl_ButtonReport_Click(object sender, RibbonControlEventArgs e)
        {
            string testSessionId = (String)this.Ctrl_DropDownTestSessions.SelectedItem.Tag;

            GenerateReport(testSessionId);
        }

        /// <summary>
        /// Ribbon closing.
        /// Release the resources.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Zen4SyncRibbon_Close(object sender, EventArgs e)
        {
            if (this._DAL != null) this._DAL.Dispose();
        }

        #endregion - Events -

        #region - Methods -

        /// <summary>
        /// Initializes the private ReportGenerator of this class.
        /// </summary>
        private void InitReportGenerator()
        {
            this._ReportGenerator = new ReportGenerator(this._DAL);
        }

        /// <summary>
        /// Initializes the database connection using the given parameters.
        /// If the connection fails, this method shows a message to the user indicating the error origin and returns false.
        /// </summary>
        /// <param name="serverName">The name of the server hosting Zen4SyncRepository.</param>
        /// <param name="useIntegratedSecurity">true or false to use or not the Integrated Security.</param>
        /// <param name="login">login if not using the Integrated Security. Give null or empty string if not using Integrated Security.</param>
        /// <param name="password">password if not using the Integrated Security. Give null or empty string if not using Integrated Security.</param>
        private bool InitDatabaseConnection(string serverName, bool useIntegratedSecurity, string login, string password)
        {
            if (this._DAL == null) this._DAL = new Zen4SyncDAL();

            // Connecting
            bool initOK = this._DAL.Init(serverName, useIntegratedSecurity, login, password);

            if (!initOK)
            {
                ShowError(String.Format("{0}\n\n{1}", Messages.DbConnectionError, this._DAL.InitException.Message));
                return false;
            }

            return true;
        }

        /// <summary>
        /// Initialize the user interface (fill controls with default data, updates visibility etc.).
        /// According to the given boolean, the "Database Connection" part of the UI is shown or hidden.
        /// This method does not affect the "Status" part of the UI.
        /// </summary>
        /// <param name="connectedToDatabase">
        /// true to show only the "Search" and "Report" groups, false to show only the "Connection" group.
        /// </param>
        private void InitUI(bool connectedToDatabase)
        {
            if (connectedToDatabase)
            {
                FillCategories();
                // Fill the Test Session DropDownList with the default message
                FillTestSessions(null);

                this.Ctrl_GroupConnection.Visible = false;
                this.Ctrl_GroupSearch.Visible = true;
                this.Ctrl_GroupReport.Visible = true;
            }
            else
            {
                this.Ctrl_GroupConnection.Visible = true;
                this.Ctrl_GroupSearch.Visible = false;
                this.Ctrl_GroupReport.Visible = false;
            }
        }

        /// <summary>
        /// Launches the database connection in a dedicated thread.
        /// This method checks the parameters validity and shows messages to the user if something is wrong.
        /// If parameters are OK, the connection is initiated in a dedicated thread.
        /// The UI is updated while the connection initialization is being performed, and updated again when the connection initialization is completed.
        /// </summary>
        /// <param name="serverName">The name of the server hosting Zen4SyncRepository.</param>
        /// <param name="useIntegratedSecurity">true or false to use or not the Integrated Security.</param>
        /// <param name="login">login if not using the Integrated Security. Give null or empty string if not using Integrated Security.</param>
        /// <param name="password">password if not using the Integrated Security. Give null or empty string if not using Integrated Security.</param>
        private void ConnectToDatabase(string serverName, bool useIntegratedSecurity, string login, string password)
        {
            // Checking parameters
            if (String.IsNullOrWhiteSpace(serverName))
            {
                ShowInformation(Messages.MandatoryDbServer);
                return;
            }

            if (!useIntegratedSecurity && String.IsNullOrWhiteSpace(login))
            {
                ShowInformation(Messages.MandatoryDbLogin);
                return;
            }

            if (!useIntegratedSecurity && String.IsNullOrWhiteSpace(password))
            {
                ShowInformation(Messages.MandatoryDbPassword);
                return;
            }

            // Parameters OK, show to the user work is happening and connect in a separate thread
            UpdateRibbonGroupState(this.Ctrl_GroupConnection, false);
            UpdateRibbonStatus(false);
            
            Task.Factory
                .StartNew(
                    () =>
                    {
                        bool connectionOK = InitDatabaseConnection(serverName,
                                               useIntegratedSecurity,
                                               login,
                                               password);
                        if (connectionOK)
                        {
                            InitReportGenerator();
                            InitUI(true);
                        }

                        // UI back to "Ready" state
                        UpdateRibbonGroupState(this.Ctrl_GroupConnection, true);
                        UpdateRibbonStatus(true);
                    });
        }

        /// <summary>
        /// According to the given boolean, Enable or Disable all the RibonItems contained in the given RibbonGroup.
        /// </summary>
        /// <param name="group">The RibbonGroup to operate on.</param>
        /// <param name="enable">true to enable all RibbonItems, false to disable.</param>
        private void UpdateRibbonGroupState(RibbonGroup group, bool enable)
        {
            foreach (var item in group.Items)
            {
                item.Enabled = enable;
            }
        }

        /// <summary>
        /// According to the given boolean, shows the user the Add-In is ready or is working.
        /// If ready is false and useWaitingCursor is true, the Mouse Pointer changes.
        /// </summary>
        /// <param name="ready">true to show the user the Add-In is ready, false to show it's working.</param>
        /// <param name="useWaitingPointer">Taken into account if ready is false. Give true to update the Mouse Pointer.</param>
        private void UpdateRibbonStatus(bool ready, bool useWaitingPointer)
        {
            this.Ctrl_ButtonReady.Visible = ready;
            this.Ctrl_ButtonWorking.Visible = !ready;

            Globals.Zen4SyncAddIn.Application.Cursor =
                (!ready && useWaitingPointer)
                ? XlMousePointer.xlWait
                : XlMousePointer.xlDefault;
        }

        /// <summary>
        /// According to the given boolean, shows the user the Add-In is ready or is working.
        /// </summary>
        /// <param name="ready">true to show the user the Add-In is ready, false to show it's working.</param>
        private void UpdateRibbonStatus(bool ready)
        {
            UpdateRibbonStatus(ready, false);
        }

        /// <summary>
        /// Fills the DropDownList of Categories.
        /// The ID of the category is stored in the Tag of the RibbonDropDownItems as a String.
        /// An empty Item is inserted at the top of the DropDownList to allow the user not to select a category
        /// </summary>
        private void FillCategories()
        {
            Ctrl_DropDownTestSessionCategories.Items.Clear();
            
            RibbonDropDownItem item = this.Factory.CreateRibbonDropDownItem();
            item.Label = String.Empty;
            item.Tag = String.Empty;
            Ctrl_DropDownTestSessionCategories.Items.Add(item);

            foreach (var category in this._DAL.GetAllTestSessionCategories())
            {
                item = this.Factory.CreateRibbonDropDownItem();
                
                item.Tag = category.testSessionCategory_id.ToString();
                item.Label = category.testSessionCategory_name;
                
                Ctrl_DropDownTestSessionCategories.Items.Add(item);
            }
        }

        /// <summary>
        /// Fills the DropDownList of Test Sessions with the given list.
        /// Give an empty or null list to fill the DropDownList with a default message.
        /// If the given list is not empty, the Id of the Test Session is stored in the Tag property of its
        /// DropDownListItem as a String.
        /// </summary>
        /// <param name="testSessions">
        /// A list of Test Sessions to insert into the DropDownList.
        /// A null or empty list to display the default message in the DropDownList.
        /// </param>
        private void FillTestSessions(List<testSession> testSessions)
        {
            this.Ctrl_DropDownTestSessions.Items.Clear();

            if (testSessions == null || testSessions.Count == 0)
            {
                RibbonDropDownItem defaultItem = this.Factory.CreateRibbonDropDownItem();
                defaultItem.Tag = EmptyTestSessionId;
                defaultItem.Label = EmtpyTestSessionLabel;
                this.Ctrl_DropDownTestSessions.Items.Add(defaultItem);
                return;
            }

            foreach (var testSession in testSessions)
            {
                RibbonDropDownItem item = this.Factory.CreateRibbonDropDownItem();
                item.Tag = testSession.testSession_id.ToString();
                item.Label = testSession.testSession_name;
                this.Ctrl_DropDownTestSessions.Items.Add(item);
            }
        }

        /// <summary>
        /// Searches all the Test Session mathching the given criterias in a dedicated thread.
        /// At least one of the given parameters has to have a value. If not, a message is displayed to the user and no search happens.
        /// If a null or empty categoryId is given, no category is used for searching.
        /// The DropDownList of Test Sessions is filled by this method with the search results.
        /// If no Test Session has been found, a message is displayed to the user.
        /// The UI is updated to show working is happening while the search is being performed.
        /// </summary>
        /// <param name="categoryId">A Test Session Category Id, or null or empty ot perform a category-free search.</param>
        /// <param name="name">Part of the name of the Test Session.</param>
        private void SearchTestSessions(string categoryId, string name)
        {
            // At least one search criteria is mandatory
            if (String.IsNullOrEmpty(categoryId) && String.IsNullOrWhiteSpace(name))
            {
                ShowInformation(Messages.MandatorySearchCriteria);
                return;
            }
            
            List<testSession> matchingTestSessions = null;

            // Show the user some work is happening
            UpdateUiStatusForReport(true);

            Task.Factory
                .StartNew(
                () =>
                {
                    matchingTestSessions = this._DAL.SearchTestSessions(
                        !String.IsNullOrEmpty(categoryId) ? new Func<testSession, bool>(t => t.testSession_testSessionCategoryId == new Guid(categoryId)) : null,
                        !String.IsNullOrWhiteSpace(name) ? new Func<testSession, bool>(t => t.testSession_name.Contains(name)) : null
                    );

                    if (matchingTestSessions.Count == 0)
                        ShowInformation(Messages.NoTestSessionsFound);

                    // If the Test Session is empty, this call will fill the Test Sessions list with the default item
                    FillTestSessions(matchingTestSessions);
                 
                    UpdateUiStatusForReport(false);
                });
            
        }

        /// <summary>
        /// Shows the given message to the user as an information.
        /// </summary>
        /// <param name="message">Message to show to the user.</param>
        private void ShowInformation(string message)
        {
            MessageBox.Show(message, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        /// <summary>
        /// Shows the given message to the user as an error.
        /// </summary>
        /// <param name="message">Message to show to the user.</param>
        private void ShowError(string message)
        {
            MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        /// <summary>
        /// Generates a report for the given testSessionId.
        /// If the given testSessionId is the default "Empty" Test Session Id, a message is displayed to
        /// the user and nothing is generated.
        /// This method asks the user confirmation before generating the report.
        /// Useful because the report will delete all the sheets of the active Workbook.
        /// </summary>
        /// <param name="testSessionId">The Id of the target Test Session.</param>
        private void GenerateReport(string testSessionId)
        {
            if (testSessionId == EmptyTestSessionId)
            {
                ShowInformation(Messages.SearchForTestSessions);
                return;
            }

            DialogResult userConfirmation = MessageBox.Show(Messages.ConfirmReportGeneration, Messages.ConfirmReportGenerationTitle, MessageBoxButtons.OKCancel);

            if (userConfirmation != DialogResult.OK)
                return;

            // Show the user some work is happening
            UpdateUiStatusForReport(true, true);

            Task.Factory
                .StartNew(
                    () =>
                    {
                        try
                        {
                            testSession selectedTestSession = this._DAL.GetTestSession(new Guid(testSessionId));
                            _ReportGenerator.GenerateReport(selectedTestSession, Globals.Zen4SyncAddIn.Application.ActiveWorkbook);
                        }
                        catch (Exception exc)
                        {
                            ShowError(exc.Message);
                        }
                        finally
                        {
                            // UI back to "Ready" state
                            UpdateUiStatusForReport(false);
                        }
                    });
        }

        /// <summary>
        /// According to the given boolean, allows or forbids the user from interacting with the
        /// "Report" and "Search" 
        /// </summary>
        /// <param name="uiWorking">true to transform the UI in a "Working" state, false to transform the UI in a "Ready" state.</param>
        private void UpdateUiStatusForReport(bool uiWorking)
        {
            UpdateUiStatusForReport(uiWorking, false);
        }

        /// <summary>
        /// According to the given boolean, allows or forbids the user from interacting with the
        /// "Report" and "Search" .
        /// If uiWorking and updateMousePointerWhenWorking are both true, the Mouse Pointer is updated.
        /// </summary>
        /// <param name="uiWorking">true to transform the UI in a "Working" state, false to transform the UI in a "Ready" state.</param>
        /// <param name="updateMousePointerWhenWorking">Taken into account if uiWorking is true. Give true to update the Mouse Pointer look.</param>
        private void UpdateUiStatusForReport(bool uiWorking, bool updateMousePointerWhenWorking)
        {
            UpdateRibbonGroupState(this.Ctrl_GroupSearch, !uiWorking);
            UpdateRibbonGroupState(this.Ctrl_GroupReport, !uiWorking);
            UpdateRibbonStatus(!uiWorking, updateMousePointerWhenWorking);
        }

        #endregion - Methods -  
    }
}
